
<?php
namespace app\server\controller;

class JT808
{
    // 消息ID定义 - JTT808-2019标准
    const MSG_ID_REGISTER = 0x0100;        // 终端注册
    const MSG_ID_REGISTER_RESP = 0x8100;   // 注册响应
    const MSG_ID_LOCATION = 0x0200;        // 位置信息汇报
    const MSG_ID_HEARTBEAT = 0x0002;        // 终端心跳
    const MSG_ID_AUTH = 0x0102;            // 终端鉴权
    
    public static $sequenceNumber = 0; // 流水号计数器

    /**
     * 将接收到的二进制数据转换为16进制数组
     */
    public function getTo16Bytes($data)
    {
        $content = bin2hex($data);
        $res = explode('7e7e', $content);
        $array = [];
        
        if (count($res) > 1) {
            foreach ($res as $k => $v) {
                if ($k == 0) {
                    $array[$k] = str_split($v . '7e', 2);
                } else if ($k == count($res) - 1) {
                    $array[$k] = str_split('7e' . $v, 2);
                } else {
                    $array[$k] = str_split('7e' . $v . '7e', 2);
                }
            }
        } else {
            $array[] = str_split($res[0], 2);
        }
        return $array;
    }

    /**
     * 获取消息ID
     */
    public function getMessageIdNumber($data)
    {
        if (count($data) < 2) return null;
        return $data[1] . $data[2];
    }

    /**
     * 获取终端设备号
     */
    public function getEquipmentNumber($data)
    {
        $equipmentArray = array_slice($data, 5, 6);
        $res = [];
        foreach ($equipmentArray as $k => $v) {
            $res[$k] = base_convert($v, 16, 10);
        }
        $equipmentNumber = $this->bcdToString($res);
        return str_pad($equipmentNumber, 12, "0", STR_PAD_LEFT);
    }

    /**
     * BCD码转字符串
     */
    public function bcdToString($data)
    {
        $len = count($data);
        $temp = "";
        for ($i = 0; $i < $len; $i++) {
            $temp .= (($data[$i] & 0xf0) >> 4);
            $temp .= ($data[$i] & 0x0f);
        }
        return (substr($temp, 0, 1) == 0) ? substr($temp, 1) : $temp;
    }

    /**
     * 计算校验码
     */
    public function checkCode($data)
    {
        $sum = 0;
        unset($data[0]); // 去掉开头的7e
        foreach ($data as $v) {
            $sum = $sum ^ hexdec($v);
        }
        return str_pad(dechex($sum), 2, "0", STR_PAD_LEFT);
    }

    /**
     * 打包注册响应消息
     */
    public function packRegisterResponse($terminalId, $resultCode, $authCode = '')
    {
        $msgBody = chr($resultCode);
        if ($resultCode == 0 && $authCode) {
            $msgBody .= $authCode;
        }
        return $this->packMessage(self::MSG_ID_REGISTER_RESP, $terminalId, $msgBody);
    }

    /**
     * 通用消息打包方法
     */
    public function packMessage($msgId, $terminalId, $msgBody, $isSubPackage = false, $encryptType = 0)
    {
        // 消息头组装
        $header = $this->packHeader($msgId, $terminalId, strlen($msgBody), $isSubPackage, $encryptType);
        $message = $header . $msgBody;
        $checkCode = $this->checkCode(str_split(bin2hex($message), 2));
        
        // JT808协议转义处理
        $escapedMessage = $this->escapeMessage($message . hex2bin($checkCode));
        return hex2bin('7e') . $escapedMessage . hex2bin('7e');
    }

    /**
     * 打包消息头
     */
    private function packHeader($msgId, $terminalId, $bodyLength, $isSubPackage, $encryptType)
    {
        $header = '';
        $header .= pack('n', $msgId);                    // 消息ID
        $header .= pack('n', $bodyLength);               // 消息体属性
        $header .= pack('H12', $terminalId);              // 终端手机号
        $header .= pack('n', self::$sequenceNumber++);  // 消息流水号
        
        if ($isSubPackage) {
            $header .= pack('n', 1); // 总包数
        $header .= pack('n', 1); // 包序号
        return $header;
    }

    /**
     * 协议转义处理
     */
    private function escapeMessage($message)
    {
        $search = [hex2bin('7d'), hex2bin('7e')];
        $replace = [hex2bin('7d01'), hex2bin('7d02')];
        return str_replace($search, $replace, $message);
    }
}
